"""
Tests for BaseDownloader abstract class and utility methods.
"""

from src.local_deep_research.research_library.downloaders.base import (
    BaseDownloader,
    ContentType,
    DownloadResult,
)


class ConcreteDownloader(BaseDownloader):
    """Concrete implementation for testing abstract BaseDownloader."""

    def can_handle(self, url: str) -> bool:
        return "test.example.com" in url

    def download(self, url: str, content_type: ContentType = ContentType.PDF):
        if "success" in url:
            return b"%PDF-1.4 test content"
        return None


class TestContentTypeEnum:
    """Tests for ContentType enum."""

    def test_pdf_value(self):
        """ContentType.PDF has correct value."""
        assert ContentType.PDF.value == "pdf"

    def test_text_value(self):
        """ContentType.TEXT has correct value."""
        assert ContentType.TEXT.value == "text"


class TestDownloadResult:
    """Tests for DownloadResult namedtuple."""

    def test_default_values(self):
        """DownloadResult has correct default values."""
        result = DownloadResult()
        assert result.content is None
        assert result.skip_reason is None
        assert result.is_success is False

    def test_success_result(self):
        """DownloadResult with successful content."""
        result = DownloadResult(content=b"test", is_success=True)
        assert result.content == b"test"
        assert result.is_success is True
        assert result.skip_reason is None

    def test_skip_result(self):
        """DownloadResult with skip reason."""
        result = DownloadResult(skip_reason="Not available")
        assert result.content is None
        assert result.is_success is False
        assert result.skip_reason == "Not available"


class TestBaseDownloaderInit:
    """Tests for BaseDownloader initialization."""

    def test_default_timeout(self):
        """Default timeout is 30 seconds."""
        downloader = ConcreteDownloader()
        assert downloader.timeout == 30

    def test_custom_timeout(self):
        """Custom timeout is set correctly."""
        downloader = ConcreteDownloader(timeout=60)
        assert downloader.timeout == 60

    def test_session_created(self):
        """requests.Session is created."""
        downloader = ConcreteDownloader()
        assert downloader.session is not None

    def test_rate_tracker_created(self):
        """AdaptiveRateLimitTracker is created."""
        downloader = ConcreteDownloader()
        assert downloader.rate_tracker is not None


class TestCanHandle:
    """Tests for can_handle method."""

    def test_can_handle_matching_url(self):
        """Returns True for matching URL."""
        downloader = ConcreteDownloader()
        assert (
            downloader.can_handle("https://test.example.com/paper.pdf") is True
        )

    def test_can_handle_non_matching_url(self):
        """Returns False for non-matching URL."""
        downloader = ConcreteDownloader()
        assert downloader.can_handle("https://other.com/paper.pdf") is False


class TestDownload:
    """Tests for download method."""

    def test_download_success(self):
        """Returns content for successful download."""
        downloader = ConcreteDownloader()
        content = downloader.download("https://test.example.com/success.pdf")
        assert content is not None
        assert b"PDF" in content

    def test_download_failure(self):
        """Returns None for failed download."""
        downloader = ConcreteDownloader()
        content = downloader.download("https://test.example.com/failure.pdf")
        assert content is None


class TestDownloadPdf:
    """Tests for download_pdf convenience method."""

    def test_download_pdf_calls_download(self):
        """download_pdf calls download with PDF content type."""
        downloader = ConcreteDownloader()
        content = downloader.download_pdf(
            "https://test.example.com/success.pdf"
        )
        assert content is not None


class TestDownloadWithResult:
    """Tests for download_with_result method."""

    def test_download_with_result_success(self):
        """Returns DownloadResult with content on success."""
        downloader = ConcreteDownloader()
        result = downloader.download_with_result(
            "https://test.example.com/success.pdf"
        )
        assert result.is_success is True
        assert result.content is not None

    def test_download_with_result_failure(self):
        """Returns DownloadResult with skip_reason on failure."""
        downloader = ConcreteDownloader()
        result = downloader.download_with_result(
            "https://test.example.com/failure.pdf"
        )
        assert result.is_success is False
        assert result.skip_reason is not None


class TestIsPdfContent:
    """Tests for _is_pdf_content helper method."""

    def test_is_pdf_content_by_content_type(self, mocker):
        """Detects PDF by content-type header."""
        downloader = ConcreteDownloader()
        response = mocker.Mock()
        response.headers = {"content-type": "application/pdf"}
        response.content = b"some content"
        assert downloader._is_pdf_content(response) is True

    def test_is_pdf_content_by_magic_bytes(self, mocker):
        """Detects PDF by magic bytes."""
        downloader = ConcreteDownloader()
        response = mocker.Mock()
        response.headers = {"content-type": "application/octet-stream"}
        response.content = b"%PDF-1.4 content"
        assert downloader._is_pdf_content(response) is True

    def test_is_not_pdf_content(self, mocker):
        """Returns False for non-PDF content."""
        downloader = ConcreteDownloader()
        response = mocker.Mock()
        response.headers = {"content-type": "text/html"}
        response.content = b"<html>Not a PDF</html>"
        assert downloader._is_pdf_content(response) is False


class TestDownloadPdfHelper:
    """Tests for _download_pdf helper method."""

    def test_download_pdf_success(self, mocker, mock_pdf_content):
        """Successfully downloads PDF."""
        downloader = ConcreteDownloader()

        mock_response = mocker.Mock()
        mock_response.status_code = 200
        mock_response.content = mock_pdf_content
        mock_response.headers = {"content-type": "application/pdf"}

        mocker.patch.object(
            downloader.session, "get", return_value=mock_response
        )
        mocker.patch.object(
            downloader.rate_tracker, "apply_rate_limit", return_value=0
        )
        mocker.patch.object(downloader.rate_tracker, "record_outcome")

        content = downloader._download_pdf("https://example.com/paper.pdf")
        assert content is not None
        assert content == mock_pdf_content

    def test_download_pdf_rate_limited(self, mocker):
        """Handles rate limiting (HTTP 429)."""
        downloader = ConcreteDownloader()

        mock_response = mocker.Mock()
        mock_response.status_code = 429
        mock_response.content = b""
        mock_response.headers = {}

        mocker.patch.object(
            downloader.session, "get", return_value=mock_response
        )
        mocker.patch.object(
            downloader.rate_tracker, "apply_rate_limit", return_value=0
        )
        mocker.patch.object(downloader.rate_tracker, "record_outcome")

        content = downloader._download_pdf("https://example.com/paper.pdf")
        assert content is None

    def test_download_pdf_not_found(self, mocker):
        """Handles HTTP 404."""
        downloader = ConcreteDownloader()

        mock_response = mocker.Mock()
        mock_response.status_code = 404
        mock_response.content = b""
        mock_response.headers = {}

        mocker.patch.object(
            downloader.session, "get", return_value=mock_response
        )
        mocker.patch.object(
            downloader.rate_tracker, "apply_rate_limit", return_value=0
        )
        mocker.patch.object(downloader.rate_tracker, "record_outcome")

        content = downloader._download_pdf("https://example.com/paper.pdf")
        assert content is None

    def test_download_pdf_timeout(self, mocker):
        """Handles request timeout."""
        import requests

        downloader = ConcreteDownloader()

        mocker.patch.object(
            downloader.session,
            "get",
            side_effect=requests.exceptions.Timeout("Connection timed out"),
        )
        mocker.patch.object(
            downloader.rate_tracker, "apply_rate_limit", return_value=0
        )
        mocker.patch.object(downloader.rate_tracker, "record_outcome")

        content = downloader._download_pdf("https://example.com/paper.pdf")
        assert content is None

    def test_download_pdf_connection_error(self, mocker):
        """Handles connection error."""
        import requests

        downloader = ConcreteDownloader()

        mocker.patch.object(
            downloader.session,
            "get",
            side_effect=requests.exceptions.ConnectionError(
                "Connection refused"
            ),
        )
        mocker.patch.object(
            downloader.rate_tracker, "apply_rate_limit", return_value=0
        )
        mocker.patch.object(downloader.rate_tracker, "record_outcome")

        content = downloader._download_pdf("https://example.com/paper.pdf")
        assert content is None


class TestExtractTextFromPdf:
    """Tests for extract_text_from_pdf static method."""

    def test_extract_text_from_valid_pdf(self, mock_pdf_content):
        """Extracts text from valid PDF (may be empty for minimal PDF)."""
        # Note: minimal PDF has no actual text content
        text = BaseDownloader.extract_text_from_pdf(mock_pdf_content)
        # Minimal PDF has no text, so result should be None or empty
        assert text is None or text == ""

    def test_extract_text_from_invalid_pdf(self):
        """Returns None for invalid PDF content."""
        invalid_content = b"This is not a PDF"
        text = BaseDownloader.extract_text_from_pdf(invalid_content)
        assert text is None

    def test_extract_text_from_empty_content(self):
        """Returns None for empty content."""
        text = BaseDownloader.extract_text_from_pdf(b"")
        assert text is None


class TestGetMetadata:
    """Tests for get_metadata method."""

    def test_get_metadata_default(self):
        """Default implementation returns empty dict."""
        downloader = ConcreteDownloader()
        metadata = downloader.get_metadata("https://example.com/paper.pdf")
        assert metadata == {}
